package reflexion;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import tools.Constantes;
import type.Struct;
import tools.XMLTool;
import type.Base64;
import type.IMyDate;
import type.MyDate;

/**
 * 
 *  Classe contenant toute la partie réflexion faites coté serveur
 *
 * 
 * @author Kevin Pozderec, Faycal Abka 
 * 
 */
public class Reflexion {

	/**
	 * Compteur servant à la création des objet différents
	 */
	private static int tmpCounter = 0;

	/**
	 * Le nom de la méthode.
	 */
	private String methodName;

	/**
	 * Liste d'objets envoyés en paramètre de l'appel de méthode
	 */
	private ArrayList<CtClass> objets;

	/**
	 * Objets internes. Hypothèse: ils sont récupérés dans l'ordre de leurs
	 * apparition dans le code de l'objet.
	 */
	private ArrayList<CtClass> internes;

	/**
	 * Paramètres de la méthodes
	 */
	private ArrayList<Object> params;

	/**
	 * Variable utilisée pour trouver les noms des champs.
	 */
	private String fieldName;

	/**
	 * La classe pool pour créer dynamiquement les objets reçus
	 */

	private ClassPool cp;

	/**
	 * Constructeur par défaut.
	 * On tient à jour une liste des objets, des paramètres ainsi que des objets internes aux objets
	 * */
	public Reflexion() {
		objets = new ArrayList<CtClass>();
		params = new ArrayList<Object>();
		internes = new ArrayList<CtClass>();
		cp = ClassPool.getDefault();
	}

	/**
	 * 
	 * Méthode qui créer les objets à partir de XML source
	 * 
	 * @param chaineXML : chaîne XML
	 * @throws CannotCompileException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	public void getObjectsFromXML(String chaineXML) throws CannotCompileException,
			InstantiationException, IllegalAccessException {
	
		Document d = XMLTool.XMLtoDOM(chaineXML);

		parcoursDOM(d.getDocumentElement());

	}

	/**
	 * Méthode qui parcourt l'arbre XML sous forme de DOM
	 * 
	 * @param n : Noeud courant
	 * @throws CannotCompileException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	private void parcoursDOM(Node n) throws CannotCompileException,
			InstantiationException, IllegalAccessException {

		String tmp;
		Element e;

		/*Lorsque le noeud est un élement*/
		if (n.getNodeType() == Node.ELEMENT_NODE) {

			e = (Element) n;
			
			tmp = e.getTagName();
			if ("methodName".equals(tmp)) {
				/*
				 * Récupérer le nom de la méthode
				 */
				methodName = e.getTextContent();
			}

			if ("object".equals(tmp)) {
				/*
				 * Récupérer les paramètres de type objet
				 */

				CtClass cc;

				/*Création de la CtClass pour l'objet qu'on lit*/
				synchronized (this) {
					cc = cp.makeClass("Object" + tmpCounter);
					tmpCounter++;
				}

				/*Remplir le CtClass avec les fields et méthodes*/
				fillCtClassObject(e, cc);				

				/*
				 * Ajouter l'oid et le type comme champs de l'objet afin de les
				 * récupérer facilement
				 *			
				 */				

				CtField oid = CtField.make(
						"public String __oid = \"" + e.getAttribute("oid")
								+ "\";", cc);
				cc.addField(oid);

				/*Vérification de la présence de type, champs optionnel*/
				if (e.hasAttribute("type")) {
					CtField type = CtField.make(
							"public String __type = \"" + e.getAttribute("type")
									+ "\";", cc);
					cc.addField(type);
				}
				
				objets.add(cc);

				/*
				 * Retirer les fils de champs objet pour ne pas les reparcourir (ils ont été lu par la méthode fillCtClassObject
				 */
				NodeList nl = e.getChildNodes();
				for (int i = 0; i < nl.getLength(); i++)
					e.removeChild(nl.item(i));
			}

			if ("int".equals(tmp) || "i4".equals(tmp)) {
				params.add(new Integer(e.getTextContent()));
			}

			if ("boolean".equals(tmp)) {				
				if ("1".equals(e.getTextContent()))
					params.add(new Boolean(true));
				else
					params.add(new Boolean(false));
			}

			if ("string".equals(tmp)) {
				params.add(new String(e.getTextContent()));
			}

			if ("double".equals(tmp)) {
				params.add(new Double(e.getTextContent()));
			}

			if ("dateTime.iso8601".equals(tmp)) {
				
				String chaineDate = e.getTextContent();
				String [] dateElement = new String[6];
				dateElement[0]=chaineDate.substring(0,4); //annee
				dateElement[1]=chaineDate.substring(4,6); //mois
				dateElement[2]=chaineDate.substring(6,8); //jour
				dateElement[3]=chaineDate.substring(9,11); //heure
				dateElement[4]=chaineDate.substring(12,14); //minute
				dateElement[5]=chaineDate.substring(15,17); //seconde
				
				IMyDate md = new MyDate(dateElement[0], dateElement[1], dateElement[2], dateElement[3], dateElement[4], dateElement[5]);
				params.add(md);
			}

			if ("base64".equals(tmp)) {
				Base64 bb = new Base64(e.getTextContent());
				params.add(bb);
			}

			if ("array".equals(tmp)) {
				int t;
				if (e.getFirstChild() != null) {
					if (e.getFirstChild().getFirstChild() != null) {
						if (e.getFirstChild().getFirstChild().getFirstChild() != null) {
							t = getArrayType(e.getFirstChild().getFirstChild()
									.getFirstChild().getNodeName());

							makeArray(e, t);
						}
					}
				}

				NodeList nl = e.getChildNodes();
				for (int i = 0; i < nl.getLength(); i++)
					e.removeChild(nl.item(i));
			}

			if ("struct".equals(tmp)) {
				/**>>>>>>>>>>>Type non terminé <<<<<<<<<*/
				Struct s = new Struct();
				// ATTENTION: il faut parcourir la arrayList dans s
				// et ne pas agir directememt sur s.
				createStructParam(e, s);
				params.add(s);

				NodeList nl = e.getChildNodes();
				for (int i = 0; i < nl.getLength(); i++)
					e.removeChild(nl.item(i));
			}
		}

		/*
		 * Parcours récursif si le noeud a des fils
		 */
		if (n.hasChildNodes()) {
			/*
			 * Récupère la liste des fils du noeud courant (liste de type
			 * NodeList)
			 */
			NodeList fils = n.getChildNodes();

			/*
			 * Parcours de la liste et appel récursif
			 */
			for (int i = 0; i < fils.getLength(); i++) {
				parcoursDOM(fils.item(i));
			}
		}
	}
	
	/**
	 * Parcours un noeud de type objet
	 * 
	 * @param n est le noeud de type objet.
	 * @throws CannotCompileException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	private void fillCtClassObject(Node n, CtClass cc)
			throws CannotCompileException, InstantiationException,
			IllegalAccessException {
		
		CtMethod method;

		if (n.getNodeType() == Node.ELEMENT_NODE) {
			Element e = (Element) n;
			String tmp = e.getTagName();
			
			if ("fields".equals(tmp)) {
				/*
				 * Récuperer les champs de l'objet
				 */
				createFieldsOfObject(e, cc);
				
				/*
				 * Retirer les fils de champs objet, pour ne pas les parcourir
				 * dans cette méthode.
				 */
				NodeList nl = e.getChildNodes();
				for (int i = 0; i < nl.getLength(); i++){
					e.removeChild(nl.item(i));
				}
			}

			if ("method".equals(tmp)) {
				/*
				 * Vérification de l'attribut langage n'est pas obligatoire ici,
				 * car par défaut, c'est du java.
				 */
				method = CtNewMethod.make(e.getTextContent(), cc);
				cc.addMethod(method);
			}

			if (e.hasChildNodes()) {

				/*
				 * Récupère la liste des fils du noeud courant (liste de type
				 * NodeList)
				 */

				NodeList fils = e.getChildNodes();

				/*
				 * Parcours de la liste et appel récursif
				 */
				for (int i = 0; i < fils.getLength(); i++) {
					fillCtClassObject(fils.item(i), cc);
				}
			}
		}
	}
	
	/**
	 * Ajout des champs à la classe nouvellement créée
	 * 
	 * @param n  Le noeuds field
	 * @param cc La classe à laquelle sont ajoutés les champs
	 * @throws CannotCompileException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	private void createFieldsOfObject(Node n, CtClass cc)
			throws CannotCompileException, InstantiationException,
			IllegalAccessException {

		CtField field;

		if (n.getNodeType() == Node.ELEMENT_NODE) {
			Element e = (Element) n;
			String tmp = e.getTagName();

			if ("field".equals(tmp)) {
				fieldName = e.getAttribute("name");
			}

			/*Noeud objet dans un objet : NON TERMINE
			 *Il faut instancier les objets conteneur et ensuite set les objets internes*/
			if ("object".equals(tmp)) {
				String s = fieldName;

				CtClass ccc;
				
				synchronized (this) {
					ccc = cp.makeClass("Object" + tmpCounter);
					tmpCounter++;
				}					
				
				ccc.addInterface(cp.makeInterface("objet."
						+ e.getAttribute("type")));
				
				/*Replissage de l'objet interne*/

				fillObjectInterne(e, ccc);
				
				/*Créer le champs objet interne*/				
				
				field = CtField.make(
						"public " + "objet." + e.getAttribute("type")
								+ " " + s + ";", cc);
								
				cc.addField(field);

				internes.add(ccc);
				
				NodeList nl = e.getChildNodes();
				for (int i = 0; i < nl.getLength(); i++){
					e.removeChild(nl.item(i));
				}
			}
			
			/**
			 * 	Choix d'implémentation :
			 * 
			 *  méthode 1 :
			 * 	CtField test = new CtField(CtClass.intType,fieldName,cc);
			 *	cc.addField(test,i.toString());
			 *
			 *	méthode 2 (méthode choisie):
			 *  field = CtField.make(
			 *			"public int " + fieldName + " = " + i.intValue() + ";",
			 *			cc);			
			 *	cc.addField(field);
			 */

			if ("int".equals(tmp)) {
				Integer i = new Integer(e.getTextContent());
				field = CtField.make(
						"public int " + fieldName + " = " + i.intValue() + ";",
						cc);			
				cc.addField(field);
			}

			if ("boolean".equals(tmp)) {
				if ("0".equals(e.getTextContent())) {
					field = CtField.make("public boolean " + fieldName
							+ " = false;", cc);
				} else {
					field = CtField.make("public boolean " + fieldName
							+ " = true;", cc);
				}
				cc.addField(field);
			}

			if ("string".equals(tmp)) {
				field = CtField.make(
						"public String " + fieldName + " = \""
								+ e.getTextContent() + "\";", cc);
				cc.addField(field);
			}

			if ("double".equals(tmp)) {
				field = CtField.make(
						"public double " + fieldName + " = "
								+ e.getTextContent() + ";", cc);
				cc.addField(field);
			}

			if ("dateTime.iso8601".equals(tmp)) {			
				
				String chaineDate = e.getTextContent();
				String [] dateElement = new String[6];
				dateElement[0]=chaineDate.substring(0,4);
				dateElement[1]=chaineDate.substring(4,6);
				dateElement[2]=chaineDate.substring(6,8);
				dateElement[3]=chaineDate.substring(9,11);
				dateElement[4]=chaineDate.substring(12,14);
				dateElement[5]=chaineDate.substring(15,17);				
				
				field = CtField.make(
						"public "+Constantes.chemin_IMyDate+" "+fieldName+" = new "+Constantes.chemin_MyDate+"(\""+dateElement[0]+"\",\""+dateElement[1]+"\",\""+dateElement[2]
						+"\",\""+dateElement[3]+"\",\""+dateElement[4]+"\",\""+dateElement[5]+"\");",cc);
				cc.addField(field);
				
			}

			if ("base64".equals(tmp)) {
				field = CtField.make("public "+Constantes.chemin_Base64+" " + fieldName
						+ " = new "+Constantes.chemin_Base64+"(\"" + e.getTextContent()
						+ "\");", cc);
				cc.addField(field);
			}

			if ("array".equals(tmp)) {
				int t;
				if (e.getFirstChild() != null) {
					if (e.getFirstChild().getFirstChild() != null) {
						if (e.getFirstChild().getFirstChild().getFirstChild() != null) {
							t = getArrayType(e.getFirstChild().getFirstChild()
									.getFirstChild().getNodeName());

							makeArray(e, cc, fieldName, t);
						}
					}
				}

				NodeList nl = e.getChildNodes();
				for (int i = 0; i < nl.getLength(); i++)
					e.removeChild(nl.item(i));
			}

			if ("struct".equals(tmp)) {
				/**EN COURS*/
				field = CtField.make("public "+Constantes.chemin_Struct+" " + fieldName + " = null;",
						cc);				
				cc.addField(field);
			}

			if (e.hasChildNodes()) {

				/*
				 * Récupère la liste des fils du noeud courant (liste de type
				 * NodeList)
				 */

				NodeList fils = e.getChildNodes();

				/*
				 * Parcours de la liste et appel récursif
				 */
				for (int i = 0; i < fils.getLength(); i++) {
					createFieldsOfObject(fils.item(i), cc);
				}
			}
		}
	}

	/**
	 * Méthode de remplissage d'un objet dans un objet
	 * @param n : noeud de l'objet interne
	 * @param cc
	 * @throws CannotCompileException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	private void fillObjectInterne(Node n, CtClass cc)
			throws CannotCompileException, InstantiationException,
			IllegalAccessException {
		String tmp;
		Element e;

		/*Noeud élément*/
		if (n.getNodeType() == Node.ELEMENT_NODE) {

			e = (Element) n;

			tmp = e.getTagName();
			
			/*Cas d'objet dans un objet : NON TERMINE*/
			if ("object".equals(tmp)) {
				
				fillCtClassObject(e, cc);		

				/*
				 * Ajouter l'oid et le type comme champs de l'objet afin de les
				 * récuprer facilement			
				 */
				
				CtField oid = CtField.make(
						"public final String __oid = \"" + e.getAttribute("oid")
								+ "\";", cc);
				cc.addField(oid);

				if (e.hasAttribute("type")) {
					CtField type = CtField.make(
							"public final String __type = \"" + e.getAttribute("type")
									+ "\";", cc);
					cc.addField(type);
				}				

				/*
				 * Retirer les fils de champs objet, pour ne pas les parcourir
				 * dans cette méthode.
				 */

				NodeList nl = e.getChildNodes();
				for (int i = 0; i < nl.getLength(); i++){
					e.removeChild(nl.item(i));
				}
				e.getParentNode().removeChild(e);
			}		

			if (n.hasChildNodes()) {
				/*
				 * Récupère la liste des fils du noeud courant (liste de type
				 * NodeList)
				 */
				NodeList fils = n.getChildNodes();

				/*
				 * Parcours de la liste et appel récursif
				 */
				for (int i = 0; i < fils.getLength(); i++) {
					fillObjectInterne(fils.item(i), cc);
				}
			}
		}
	}	
	
	/**
	 * Fabrique un champs tabeau
	 * 
	 * @param e
	 *            : Le DOM représentant le tableau
	 * @param cc
	 *            : La CtClass à laquelle le tableau sera ajouté
	 * @param name
	 *            : Le nom du tableau
	 * @param t
	 *            : sert à déscriminer le type des éléments
	 * @throws CannotCompileException
	 */
	private void makeArray(Node e, CtClass cc, String name, int t)
			throws CannotCompileException {

		switch (t) {
		case Constantes.INT: {
			addArray(cc, e, name, "int");
			break;
		}
		case Constantes.BOOLEAN: {
			addArray(cc, e, name, "boolean");
			break;
		}
		case Constantes.DOUBLE: {
			addArray(cc, e, name, "double");
			break;
		}
		case Constantes.STRING: {
			addArray(cc, e, name, "String");
			break;
		}
		case Constantes.BASE64: {
			addArray(cc, e, name, "Base64");
			break;
		}
		case Constantes.DATE: {
			addArray(cc, e, name, "Date");
			break;
		}
		case Constantes.STRUCT: {
			addArray(cc, e, name, "Struct");
			break;
		}
		case Constantes.ARRAY: {
			System.err.println("Les tableaux de tableaux ne sont pas implémentés");
			break;
		}
		case Constantes.OBJECT: {
			addArray(cc, e, name, "Object");
			break;
		}
		}
	}
	
	/**
	 * renvoyer le type des éléments d'un tableau
	 * 
	 * @param s : String
	 * @return Constante représentant le type de l'objet
	 */
	private int getArrayType(String s) {
		if ("int".equals(s) || "i4".equals(s))
			return Constantes.INT;
		if ("boolean".equals(s))
			return Constantes.BOOLEAN;
		if ("string".equals(s))
			return Constantes.STRING;
		if ("double".equals(s))
			return Constantes.DOUBLE;
		if ("date.iso8601".equals(s))
			return Constantes.DATE;
		if ("base64".equals(s))
			return Constantes.BASE64;
		if ("array".equals(s))
			return Constantes.ARRAY;
		if ("struct".equals(s))
			return Constantes.STRUCT;
		return Constantes.OBJECT;
	}
	
	
	/**
	 * Ajouter à l'arrayList un tableau comme paramètre
	 * 
	 * @param e
	 * @param t
	 */
	private void makeArray(Node e, int t) {

		if (e.getFirstChild() != null) {
			NodeList nl = e.getFirstChild().getChildNodes();

			switch (t) {
			case Constantes.INT: {

				ArrayList<Integer> entiers = new ArrayList<Integer>();

				for (int i = 0; i < nl.getLength(); i++) {
					entiers.add(new Integer(nl.item(i).getTextContent()
							.toString()));
				}
				params.add(entiers.toArray());
				break;

			}
			case Constantes.BOOLEAN: {
				ArrayList<Boolean> booleens = new ArrayList<Boolean>();

				for (int i = 0; i < nl.getLength(); i++) {
					booleens.add(new Boolean(nl.item(i).getTextContent()
							.toString().equals("1")));
				}
				params.add(booleens.toArray());
				break;
			}
			case Constantes.DOUBLE: {
				ArrayList<Double> doubles = new ArrayList<Double>();

				for (int i = 0; i < nl.getLength(); i++) {
					doubles.add(new Double(nl.item(i).getTextContent()
							.toString()));
				}
				params.add(doubles.toArray());
				break;
			}
			case Constantes.STRING: {
				ArrayList<String> strings = new ArrayList<String>();

				for (int i = 0; i < nl.getLength(); i++) {
					strings.add(new String(nl.item(i).getTextContent()
							.toString()));
				}
				params.add(strings.toArray());
				break;
			}
			case Constantes.BASE64: {
				ArrayList<Base64> base64 = new ArrayList<Base64>();

				for (int i = 0; i < nl.getLength(); i++) {
					base64.add(new Base64(((Element) nl.item(i))
							.getTextContent()));
				}
				params.add(base64.toArray());
				break;
			}
			case Constantes.DATE: {
				ArrayList<Date> dates = new ArrayList<Date>();
				DateFormat df = new SimpleDateFormat("yyyymmdd'T'hh:mm:ss");

				for (int i = 0; i < nl.getLength(); i++) {
					Date d = null;
					try {
						d = df.parse(e.getTextContent());
						dates.add(d);
					} catch (DOMException e1) {
						e1.printStackTrace();
					} catch (ParseException e1) {
						e1.printStackTrace();
					}
				}
				params.add(dates.toArray());
				break;
			}
			case Constantes.STRUCT: {
				ArrayList<Struct> structs = new ArrayList<Struct>();

				for (int i = 0; i < nl.getLength(); i++) {
					Struct s = new Struct();
					createStructParam(nl.item(i), s);
					structs.add(s);
				}
				params.add(structs.toArray());
				break;
			}
			case Constantes.ARRAY: {
				/**NON TERMINE*/
				break;

			}
			}

			nl = e.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				e.removeChild(nl.item(i));
			}
		}
	}
	
	
	/**
	 * Ajouter un tableau comme champ d'une CtClass
	 * 
	 * @param cc
	 *            : La CtClass à laquelle le tableau est ajouté
	 * @param e
	 *            : La partie du DOM représentant l'arbre
	 * @param name
	 *            : Le nom du tableau (du champs
	 * @param dest
	 *            : type du tableau
	 * @throws CannotCompileException
	 */
	private void addArray(CtClass cc, Node e, String name, String dest)
			throws CannotCompileException {
		CtField array;

		if (e.getFirstChild() != null) {
			NodeList nl = e.getFirstChild().getChildNodes();
			StringBuffer right_assignment = new StringBuffer();

			right_assignment.append("new " + dest + "[]{");
			for (int i = 0; i < nl.getLength(); i++) {
				if (i != (nl.getLength() - 1)) {
					if ("String".equals(dest)) {
						right_assignment.append("\""
								+ nl.item(i).getFirstChild().getFirstChild()
										.getTextContent() + "\", ");
					} else {
						right_assignment.append(""
								+ nl.item(i).getFirstChild().getFirstChild()
										.getTextContent() + ", ");
					}
				} else {
					if ("String".equals(dest)) {
						right_assignment.append("\""
								+ nl.item(i).getFirstChild().getFirstChild()
										.getTextContent() + "\"");
					} else {
						right_assignment.append(""
								+ nl.item(i).getFirstChild().getFirstChild()
										.getTextContent());
					}
				}
			}
			right_assignment.append("};");
			array = CtField.make("public " + dest + "[] " + name + " = "
					+ right_assignment.toString(), cc);
			cc.addField(array);

			nl = e.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				e.removeChild(nl.item(i));
			}
		}
	}
	
	private void createStructParam(Node n, Struct s) {

		if (n.getNodeType() == Node.ELEMENT_NODE) {
			Element e = (Element) n;

			NodeList nl = e.getElementsByTagName("member");
			for (int i = 0; i < nl.getLength(); i++) {
				String name = null;
				Object o = null;
				String t = null;

				NodeList fils = nl.item(i).getChildNodes();
				for (int j = 0; j < fils.getLength(); j++) {
					if ("value".equals(((Element) fils.item(j)).getTagName())) {
						o = ((Element) fils.item(j)).getTextContent();
						t = ((Element) fils.item(j)).getFirstChild()
								.getNodeName();
					}
					if ("name".equals(((Element) fils.item(j)).getTagName())) {
						name = ((Element) fils.item(j)).getTextContent();
					}

					if ("int".equals(t) | "i4".equals(t)) {
						new Struct(name, o, Constantes.INT, s.getMembres());
					}

					if ("boolean".equals(t)) {
						new Struct(name, o, Constantes.BOOLEAN, s.getMembres());
					}

					if ("string".equals(t)) {
						new Struct(name, o, Constantes.STRING, s.getMembres());
					}

					if ("double".equals(t)) {
						new Struct(name, o, Constantes.DOUBLE, s.getMembres());
					}

					if ("base64".equals(t)) {
						new Struct(name, o, Constantes.BASE64, s.getMembres());
					}

					if ("array".equals(t)) {
						new Struct(name, o, Constantes.ARRAY, s.getMembres());
					}

					if ("struct".equals(t)) {
						new Struct(name, o, Constantes.STRUCT, s.getMembres());
					}
				}
			}

		}
	}
	
	
	public String getMethodName() {
		return methodName;
	}

	public ArrayList<CtClass> getObjets() {
		return objets;
	}

	public ClassPool getCp() {
		return cp;
	}

	public ArrayList<Object> getParams() {
		return params;
	}

	public ArrayList<CtClass> getInternes() {
		return internes;
	}
}
